﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;

using MFiles.Server.Extensions;

namespace AutotaskSource
{
    public class AutotaskSource : BasicDataSourceConnection
    {
        private List<DataItemSimple> m_items;
        private string m_username;
        private string m_password;
        private string m_url;
        private string m_entityType;
        private net.autotask.webservices.ATWS myService;
        private Dictionary<int, string> m_columnMap =
            new Dictionary<int, string>();  //!< Maps column ordinals to property names.

        #region Public Methods
        /// <summary>
        /// Opens a connection.
        /// <param name="connectionString">Data source specific connection string.</param>
        /// /// <param name="configurationId">The GUID that identifies the data source type.</param>
        /// </summary>
        public override void OpenConnection(string connectionString, Guid guidDSType)
        {
            // Validate parameters.
            IDictionary<string, string> settings = ParseSettings(connectionString);
            if (settings.ContainsKey("username") == false) ThrowMissingParameterException("username");
            if (settings.ContainsKey("password") == false) ThrowMissingParameterException("password");
            if (settings.ContainsKey("url") == false) ThrowMissingParameterException("url");

            // Set password and username.
            settings.TryGetValue("username", out m_username);
            settings.TryGetValue("password", out m_password);
            settings.TryGetValue("url", out m_url);

            string strWebURI = m_url; // e.g. "https://webservices.autotask.net/atservices/1.5/atws.asmx";
            myService = new net.autotask.webservices.ATWS();
            myService.Url = strWebURI;
            System.Net.NetworkCredential cred = new System.Net.NetworkCredential(m_username, m_password);
            System.Net.CredentialCache credCache = new System.Net.CredentialCache();
            credCache.Add(new Uri(myService.Url), "Basic", cred);
            myService.Credentials = credCache;

            // Empty dictionaries
            m_columnMap.Clear();
        }

        /// <summary>
        /// Closes the connection.
        /// </summary>
        public override void CloseConnection()
        {
        }

        /// <summary>
        /// Prepares this data source for data retrieval using the specified select statement.
        /// <param name="szSelectStatement">Specifies the select statement.</param>
        /// </summary>
        public override void PrepareForDataRetrieval(
            string selectStatement
        )
        {

            // Parse and validate the statement.
            IDictionary<string, string> settings = ParseSettings(selectStatement);
            if (settings.ContainsKey("type") == false)
                ThrowMissingParameterException("type");
            settings.TryGetValue("type", out m_entityType);

        }

        /// <summary>
        /// Gets the available columns from the currently open selection.
        /// </summary>
        /// <returns>Collection of available columns.</returns>
        public override IEnumerable<ColumnDefinition> GetAvailableColumns()
        {

            // Get fields from the web service for a specific entity type.
            DataSet MetaDataSet = new DataSet();
            DataTable EntityDataTable = null;
            DataRow FieldDataRow = null;
            net.autotask.webservices.EntityInfo[] EntityInfo = null;
            EntityInfo = myService.getEntityInfo();
            foreach (net.autotask.webservices.EntityInfo ent in EntityInfo)
            {
                if (ent.Name.ToLower() == m_entityType.ToLower())
                {
                    EntityDataTable = MetaDataSet.Tables.Add(m_entityType);
                    EntityDataTable.Columns.Add("name");
                    EntityDataTable.Columns.Add("type");
                    EntityDataTable.Columns.Add("isRequired");
                    EntityDataTable.Columns.Add("isUDF");
                    EntityDataTable.Columns.Add("datatype");
                    net.autotask.webservices.Field[] FieldInfo = null;
                    FieldInfo = myService.GetFieldInfo(ent.Name);
                    foreach (net.autotask.webservices.Field fld in FieldInfo)
                    {
                        FieldDataRow = EntityDataTable.NewRow();
                        FieldDataRow["Name"] = fld.Name.ToString();
                        FieldDataRow["type"] = fld.Type.ToString();
                        FieldDataRow["isRequired"] = fld.IsRequired;
                        FieldDataRow["isUDF"] = string.Empty;
                        FieldDataRow["datatype"] = fld.Type;
                        EntityDataTable.Rows.Add(FieldDataRow);
                    }
                } // if entity name matches
            }

            // Get columns
            int ordinal = 1;
            foreach (DataRow row in MetaDataSet.Tables[m_entityType].Rows)
            {
                m_columnMap.Add(ordinal, (string)row.ItemArray[0]);
                Type datatype = typeof(string);
                datatype = GetDataType((string)row.ItemArray[4]);

                yield return new ColumnDefinition { Ordinal = ordinal++, Name = (string)row.ItemArray[0], Type = datatype };
            }
            
        }

        /// <summary>
        /// Gets the items as specified by the select statement.
        /// </summary>
        /// <returns>Items from the data source</returns>
        public override IEnumerable<DataItem> GetItems()
        {

            m_items = new List<DataItemSimple>();

            bool boolQueryFinished = false;
            string szCurrentId = "0";
            while (!boolQueryFinished)
            {
                string szQuery = "<queryxml><entity>" + m_entityType + "</entity><query><field>id<expression op=\"greaterthan\">" + szCurrentId + "</expression></field></query></queryxml>";

                net.autotask.webservices.ATWSResponse r = default(net.autotask.webservices.ATWSResponse);
                r = myService.query(szQuery);
                if (r.EntityResults.Length > 0)
                {
                    foreach (net.autotask.webservices.Entity ent in r.EntityResults)
                    {

                       Dictionary<int, object> dicObject = new Dictionary<int, object>();
                       foreach (KeyValuePair<int, string> kvp in m_columnMap)
                       {
                           string szValue = "";
                           if ((ent.GetType().GetProperty(kvp.Value)).GetValue(ent, null) != null)
                           {
                               szValue = (ent.GetType().GetProperty(kvp.Value)).GetValue(ent, null).ToString();
                           }
                            dicObject.Add(kvp.Key, szValue);
                        }

                        m_items.Add(new DataItemSimple(dicObject));                            
                        
                        szCurrentId = ent.id.ToString();
                    }
                }
                else
                {
                    boolQueryFinished = true;
                }

            }

            return m_items.Cast<DataItem>();
        }

        /// <summary>
        /// Checks if the data source supports fetching only one item.
        /// </summary>
        /// <returns>Returns true if the data source supports retrieving one item at the time.</returns>
        public override bool CanGetOneItem() { return true; }

        /// <summary>
        /// Gets one single item with the give identity.
        /// </summary>
        /// <param name="columnOrdinalExtID">The column ordinal of external ID.</param>
        /// <param name="externalID">The external ID of the item that is requested.</param>
        /// <returns>Receives the item or null if the item was not found.</returns>
        public override DataItem GetOneItem(int columnOrdinalExtID, string externalID)
        {
            // Return the requested item.
            return m_items
                   .Where(item =>
                           item.GetValue(columnOrdinalExtID).ToString().ToUpper().Equals(externalID.ToUpper()))
                   .Cast<DataItem>()
                   .FirstOrDefault();
        }

        #endregion
        #region Private methods
        /// <summary>
        /// Parses the given string for settings.
        /// </summary>
        /// <param name="settingsString">String to parse</param>
        /// <returns>identified key-value pairs</returns>
        private static IDictionary<string, string> ParseSettings(string settingsString)
        {
            // Parse the requested and return is key-value pairs.
            Regex pairRegex = new Regex(@"^\s*(?<type>\w*)\s*=(?<value>.*)$");
            IDictionary<string, string> settings = settingsString.Split(';')
                    .Select(pair => pairRegex.Match(pair))  // Search for key-value pairs.
                    .Where(m => m.Success)  // Only valid matches.
                    .ToDictionary(m => m.Groups["type"].Value.ToLower(), m => m.Groups["value"].Value);  // Convert to key-value dictionary
            return settings;
        }

        /// <summary>
        /// Throws missing parameter exception.
        /// </summary>
        /// <param name="parameter">The name of the missing parameter.</param>
        /// <returns></returns>
        private static void ThrowMissingParameterException(string parameter)
        {
            // Throw.
            throw new ArgumentException(String.Format("Missing parameter: '{0}'", parameter));
        }

        /// <summary>
        /// Returns datatype for known items.
        /// </summary>
        /// <returns></returns>
        private static Type GetDataType(string parameter)
        {
            if (parameter == "long") return typeof(int);
            if (parameter == "short") return typeof(int);
            if (parameter == "datetime") return typeof(DateTime);
            if (parameter == "integer") return typeof(int);
            if (parameter == "string") return typeof(string);
            if (parameter == "double") return typeof(double);
            if (parameter == "boolean") return typeof(bool);
            return typeof(string);
        }

        #endregion
    }
}
